package demo.java.time.format;

import demo.java.time.DateTimeDemo;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Date;

public class DateTimeFormatterDemo {

    private static Logger logger = LoggerFactory.getLogger(DateTimeFormatterDemo.class);

    /**
     * yyyy-MM-dd HH:mm:ss
     */
    public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * yyyy-MM-dd HH:mm:ss
     */
    public static final DateTimeFormatter yyyyMMdd_HHmmss_SSS = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS");

    /**
     * parseUTC
     *
     * @param utcStr . UTC 时间格式，如 2023-03-08T00:00:00Z
     * @return
     */
    public static long parseUTC(String utcStr) {
        TemporalAccessor temporalAccessor = DateTimeFormatter.ISO_INSTANT.parse(utcStr);
        Instant instant = Instant.from(temporalAccessor);
        return instant.toEpochMilli();
    }

    /**
     * T表示分隔符，Z表示的是UTC。
     * <p>
     * UTC：世界标准时间，在标准时间上加上8小时，即东八区时间，也就是北京时间。
     */
    @Test
    public void testParseUTC() {

        String utcStr = "2023-03-08T00:00:00Z";

        System.out.println("UTC = " + utcStr);
        DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;

        TemporalAccessor temporalAccessor = DateTimeFormatter.ISO_INSTANT.parse(utcStr);
        System.out.println("TemporalAccessor = " + temporalAccessor);
        Instant instant = temporalAccessor.query(Instant::from);
        System.out.println("Instant = " + instant);
        System.out.println("OffsetDateTime = " + instant.atOffset(ZoneOffset.ofHours(8)));
        System.out.println("EpochMilli = " + instant.toEpochMilli());
        System.out.println("parseUTC = " + parseUTC(utcStr));
        System.out.println("leap second parsed="
                + temporalAccessor.query(DateTimeFormatter.parsedLeapSecond()));

    }

    /**
     * <li>Instant 表示时间轴上的瞬时点，需要完整的时区信息
     * <li>LocalDateTime 不包含时区信息，需要配合 atZone() 指定时区
     * <li>始终明确时区，避免依赖系统默认时区带来的不确定性
     */
    @Test
    public void testParseStrToTs() {
        long ts = LocalDateTime
                .parse("2025-10-16 15:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
                .atZone(ZoneId.systemDefault()) // 使用系统默认时区
                .toInstant()
                .toEpochMilli();
        System.out.println(ts);

        ts = LocalDateTime
                .parse("2025-10-16 15:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
                .atZone(ZoneId.of("Asia/Shanghai")) // 明确指定时区
                .toInstant()
                .toEpochMilli();
        System.out.println(ts);

        ts = LocalDateTime
                .parse("2025-10-16 15:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
                .atZone(ZoneOffset.of("+8")) // 明确指定时区
                .toInstant()
                .toEpochMilli();
        System.out.println(ts);

        ts = ZonedDateTime
                .parse("2025-10-16 15:00:00+08:00",
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssXXX"))
                .toInstant()
                .toEpochMilli();
        System.out.println(ts);

        ts = Instant.parse("2025-10-16T15:00:00Z").toEpochMilli();
        System.out.println(ts);
    }

    @Test
    public void format() {
        logger.info(yyyyMMdd_HHmmss_SSS.format(LocalDateTime.now()));
        logger.info(dateTimeFormatter.format(LocalDateTime.now()));
        String start = LocalDateTime
                .ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.ofHours(8))
                .toInstant(ZoneOffset.UTC)
                .toString();
        System.out.println(start);

    }

    /**
     * @param text
     * @param formatter
     * @return
     */
    public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
        if (formatter == null) {
            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        }
        return LocalDateTime.parse(text, formatter);
    }

    /**
     * @param text
     * @param formatter
     * @return
     */
    public static Date parse2Date(CharSequence text, DateTimeFormatter formatter) {
        if (formatter == null) {
            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        }
        LocalDateTime localDatetime = parse(text, formatter);
        return DateTimeDemo.from(localDatetime);
    }


    /**
     * Attempts to parse a timestamp from a given string
     * Formats accepted are:
     * <ul>
     * <li>Relative: {@code 5m-ago}, {@code 1h-ago}, etc. See
     * {@link #parseDuration}</li>
     * <li>Absolute human readable dates:
     * <ul><li>"yyyy/MM/dd-HH:mm:ss"</li>
     * <li>"yyyy/MM/dd HH:mm:ss"</li>
     * <li>"yyyy/MM/dd-HH:mm"</li>
     * <li>"yyyy/MM/dd HH:mm"</li>
     * <li>"yyyy-MM-dd"</li></ul></li>
     * <li>Unix Timestamp in seconds or milliseconds:
     * <ul><li>1355961600</li>
     * <li>1355961600000</li>
     * <li>1355961600.000</li></ul></li>
     * </ul>
     *
     * @param datetime The string to parse a value for
     * @return A Unix epoch timestamp in milliseconds
     * @throws IllegalArgumentException if the request was malformed
     */
    public static long parseDateTimeString(final String datetime) {
        if (datetime == null || datetime.isEmpty()) {
            return -1;
        }
        if ("now".equalsIgnoreCase(datetime)) {
            return System.currentTimeMillis();
        }
        if (datetime.toLowerCase().endsWith("-ago")) {
            long interval = parseDuration(datetime.substring(0, datetime.length() - 4));
            return System.currentTimeMillis() - interval;
        }
        if (datetime.contains("/")) {
            return parseDateTime(datetime);
        } else if (datetime.contains("-")) {
            return parseDateTime2(datetime);
        } else {
            return parseDateTime3(datetime);
        }
    }


    /**
     * 解析yyyy/MM/dd格式的时间
     *
     * @param datetime
     * @return
     */
    private static long parseDateTime(String datetime) {
        try {
            SimpleDateFormat fmt = null;
            switch (datetime.length()) {
                case 10:
                    fmt = new SimpleDateFormat("yyyy/MM/dd");
                    break;
                case 16:
                    fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm");
                    break;
                case 19:
                    fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    break;
                default:
                    throw new IllegalArgumentException("Invalid absolute date: " + datetime);
            }
            return fmt.parse(datetime).getTime();
        } catch (ParseException e) {
            throw new IllegalArgumentException("Invalid date: " + datetime
                    + ". " + e.getMessage());
        }
    }

    /**
     * 解析yyyy-MM-dd格式的时间
     *
     * @param datetime
     * @return
     */
    private static long parseDateTime2(String datetime) {
        try {
            SimpleDateFormat fmt = null;
            switch (datetime.length()) {
                case 10:
                    fmt = new SimpleDateFormat("yyyy-MM-dd");
                    break;
                case 16:
                    fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                    break;
                case 19:
                    fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    break;
                default:
                    throw new IllegalArgumentException("Invalid absolute date: " + datetime);
            }
            return fmt.parse(datetime).getTime();
        } catch (ParseException e) {
            throw new IllegalArgumentException("Invalid date: " + datetime
                    + ". " + e.getMessage());
        }
    }


    /**
     * 解析时间戳，如：
     * Unix Timestamp in seconds or milliseconds:
     * <ul>
     *     <li>1355961600</li>
     *     <li>1355961600000</li>
     *     <li>1355961600.000</li>
     * </ul>
     *
     * @param datetime
     * @return
     */
    private static long parseDateTime3(String datetime) {
        try {
            long time;
            final boolean contains_dot = datetime.contains(".");
            // [0-9]{10} ten digits
            // \\. a dot
            // [0-9]{1,3} one to three digits
            final boolean valid_dotted_ms = datetime.matches("^[0-9]{10}\\.[0-9]{1,3}$");
            if (contains_dot) {
                if (!valid_dotted_ms) {
                    throw new IllegalArgumentException("Invalid time: " + datetime
                            + ". Millisecond timestamps must be in the format "
                            + "<seconds>.<ms> where the milliseconds are limited to 3 digits");
                }
                time = Long.parseLong(datetime.replace(".", ""));
            } else {
                time = Long.parseLong(datetime);
            }
            if (time < 0) {
                throw new IllegalArgumentException("Invalid time: " + datetime
                        + ". Negative timestamps are not supported.");
            }
            // this is a nasty hack to determine if the incoming request is
            // in seconds or milliseconds. This will work until November 2286
            if (datetime.length() <= 10) {
                time *= 1000;
            }
            return time;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid time: " + datetime
                    + ". " + e.getMessage());
        }
    }


    /**
     * Parses a human-readable duration (e.g, "10m", "3h", "14d") into seconds.
     * <p>
     * Formats supported:<ul>
     * <li>{@code ms}: milliseconds</li>
     * <li>{@code s}: seconds</li>
     * <li>{@code m}: minutes</li>
     * <li>{@code h}: hours</li>
     * <li>{@code d}: days</li>
     * <li>{@code w}: weeks</li>
     * <li>{@code n}: month (30 days)</li>
     * <li>{@code y}: years (365 days)</li></ul>
     *
     * @param duration The human-readable duration to parse.
     * @return A strictly positive number of milliseconds.
     * @throws IllegalArgumentException if the interval was malformed.
     */
    public static final long parseDuration(final String duration) {
        long interval;
        long multiplier;
        double temp;
        int unit = 0;
        while (Character.isDigit(duration.charAt(unit))) {
            unit++;
            if (unit >= duration.length()) {
                throw new IllegalArgumentException("Invalid duration, must have an "
                        + "integer and unit: " + duration);
            }
        }
        try {
            interval = Long.parseLong(duration.substring(0, unit));
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid duration (number): " + duration);
        }
        if (interval <= 0) {
            throw new IllegalArgumentException("Zero or negative duration: " + duration);
        }
        switch (duration.toLowerCase().charAt(duration.length() - 1)) {
            case 's':
                if (duration.charAt(duration.length() - 2) == 'm') {
                    return interval;
                }
                multiplier = 1;
                break;                        // seconds
            case 'm':
                multiplier = 60;
                break;               // minutes
            case 'h':
                multiplier = 3600;
                break;             // hours
            case 'd':
                multiplier = 3600 * 24;
                break;        // days
            case 'w':
                multiplier = 3600 * 24 * 7;
                break;    // weeks
            case 'n':
                multiplier = 3600 * 24 * 30;
                break;   // month (average)
            case 'y':
                multiplier = 3600 * 24 * 365;
                break;  // years (screw leap years)
            default:
                throw new IllegalArgumentException("Invalid duration (suffix): " + duration);
        }
        multiplier *= 1000;
        temp = (double) interval * multiplier;
        if (temp > Long.MAX_VALUE) {
            throw new IllegalArgumentException("Duration must be < Long.MAX_VALUE ms: " + duration);
        }
        return interval * multiplier;
    }

    /**
     * @param dateTime
     * @param formatter
     * @return
     */
    public static String format(LocalDateTime dateTime, DateTimeFormatter formatter) {
        if (formatter == null) {
            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        }
        return formatter.format(dateTime);
    }

    @Test
    public void demo() {
        System.out.println(dateTimeFormatter.toString());

        LocalTime localTime = LocalTime.parse("2018-02-08 12:00:00", dateTimeFormatter);
        System.out.println("localTime = " + localTime);

        long secondOfDay = localTime.getLong(ChronoField.SECOND_OF_DAY);
        System.out.println("secondOfDay = " + secondOfDay);

        LocalDate localDate = LocalDate.parse("2011-12-03T10:15:30", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println("localDate = " + localDate);

        LocalDateTime localDateTime = LocalDateTime.parse("2011-12-03T10:15:30", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println("localDateTime = " + localDateTime);
        System.out.println("EpochSecond = " + localDateTime.toEpochSecond(ZoneOffset.of("+8")));

        Long newSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        logger.info("EpochSecond = {},currentTimeMillis = {}", newSecond, System.currentTimeMillis());

        long tmp = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        System.out.println(tmp);
        System.out.println(new Date(tmp * 1000));
    }

    @Test
    public void testParse() {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd H:mm:ss");
        System.out.println(LocalDateTime.parse("2020-10-23 9:35:26", dateTimeFormatter));
        System.out.println(LocalDateTime.parse("2020-10-23 09:35:26", dateTimeFormatter));
        System.out.println(LocalDateTime.parse("2020-10-23 19:35:26", dateTimeFormatter));

        System.out.println(parseDateTimeString("now"));
        System.out.println(parseDateTimeString("1d-ago"));
        System.out.println(parseDateTimeString("2020/10/23 19:35:26"));
        System.out.println(parseDateTimeString("2020-10-23 19:35"));
        System.out.println(parseDateTimeString("2020/10/23"));
        System.out.println(parseDateTimeString("" + System.currentTimeMillis()));
    }

    /**
     * 在java8之前，时间日期的格式化非常麻烦，经常使用SimpleDateFormat来进行格式化，但是SimpleDateFormat并不是线程安全的。
     * 在java8中，引入了一个全新的线程安全的日期与时间格式器。并且预定义好了格式。在DateTimeFormatter中还有很多定义好的格式。
     */
    @Test
    public void demoDateTimeFormatter() {
        String formatNow = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        System.out.println(formatNow);

        LocalDate localDate = LocalDate.parse("20180101", DateTimeFormatter.ofPattern("yyyyMMdd"));
        System.out.println(localDate);

        LocalDate localDate2 = LocalDate.parse("20180101", DateTimeFormatter.BASIC_ISO_DATE);
        System.out.println(localDate2);
        ;
    }

}
